# NirvanixAPI.tcl --
#
#	Nirvanix API interface in TCL.
#
# Copyright (c) 2009 Nirvanix
# http://www.nirvanix.com/
#
# $Id$

package provide nirvanix 1.0

package require http

namespace eval nirvanix {
	namespace export executeMethod getValue uploadFile downloadFile
	# This is a VERY simple implementation that should work when not parsing much data.
	proc parseResponse {text {handleerror true}} {
		set text [string map {[ [ ] ]} $text]

		while {[regsub -all {<?<(\S+?)(.*?)>(.*?)</\1>} $text "\{[list \\1 \\2 \\3]\}" text]} {
		  	# do nothing
		}
		if {$handleerror} {
			set responseCode [nirvanix::getValue $text ResponseCode]
			if {$responseCode != 0} {
				set errorMsg [nirvanix::getValue $text ErrorMessage]
				puts stdout "ResponseCode: $responseCode"
				puts stdout "Error Message: $errorMsg"
				error $errorMsg
			}
		}
		return $text 
	}

	proc atomic? {list} {string equal $list [lindex $list 0]}

	proc lrsearch2 {list el {prefix {}} } {
	   set pos [lsearch $list $el]
	   if {-1 != $pos} {return [concat $prefix $pos]}
	   set i 0
	   foreach ilist $list {
	       if {![atomic? $ilist]} {
	           set pos [lrsearch2 $ilist $el [concat $prefix $i]]
	           if {-1 != $pos} {return $pos}
	       }
	       incr i
	   }
	   return -1
	}
}
	# nirvanix::executeMethod --
	#
	# Calls an API method under a specific nirvanix namespace with
	# the provided params.  If installed SSL can be used with the
	# secure flag when calling the web methods.
	#
	# Arguments:
	# namespace The Nirvanix namespace where the method is located (IMFS, Authentication, etc).
	# method 	The method in the Nirvanix namespace to call.
	# params	A name / value array of parameters that will be passed to the method.
	# secure	if HTTPS should be used or not.
	#
	# Results:
	# The return value is the body of the xml response turned into an array.
	# Individual simple values can be retrieved by calling nirvanix::GetValue result element
	proc nirvanix::executeMethod {namespace method params secure} {
		http::config -useragent "Nirvanix Tcl SDK"
		if {$secure} { set url "https" } else { set url "http" }
		set url "$url://services.nirvanix.com/ws/$namespace/$method.ashx?"
		foreach {name value} $params {
			set url "$url$name=$value&"
		}
		#puts stdout "Request URL: $url"
		set token [http::geturl $url]
		set body [http::data $token]
	    http::cleanup $token
		return [parseResponse $body]
	} 

	# nirvanix::getValue --
	#
	# A very simple proc to retrieve the first matching nodes body in the
	# returned data that corresponds to the name passed in element.
	#
	# If you are searching for SessionToken you would pass the results of ExecuteMethod
	# as nirvanix::GetValue $results SessionToken to do the lookup.
	#
	# Arguments:
	# data		The value returned from ExecuteMethod.
	# element 	The node you are looking for.
	#
	# Results:
	# The body of the element that is found.
	#
	proc nirvanix::getValue {data element} { 
		set position [lrsearch2 $data $element]
		set position [string range $position 0 [expr [string length $position]-3]]
		set position "$position 2"
		return [lindex $data $position]
	}

	# nirvanix::uploadFile --
	#
	# Uploads a single file up to 256gb to Nirvanix.
	#
	# Arguments:
	# filepath		The local path to the file.
	# posturl		the URL containing the Upload Node and Upload Token along with destination path.
	# progressCallback	The callback that will be updated with progress as the upload is done.
	#
	# Results:
	# Body of XML that can be read using nirvanix::GetValue
	proc nirvanix::uploadFile {filepath destFolderPath destFilename host token progressCallback} {
		set posturl "http://$host/Upload.ashx?uploadToken=$token&destFolderPath=$destFolderPath"
		set fd [open "${filepath}" r]
	    fconfigure $fd -translation binary -encoding binary
		# You can add -proxyport and -proxyhost to support proxies here.
		http::config -useragent "Nirvanix Tcl SDK"
		set numBytes 0
		set buffersize [expr 1024*1024*4]
	    set fileLength [file size $filepath]
		while {$numBytes < $fileLength} {
			set data [read $fd $buffersize]
			set outputData {}
			
		    set bound "XYZBOUNDARY23487844XYZBOUNDARY1234567889"
		    append outputData "----$bound\r\n"
			append outputData "Content-Disposition: form-data; name=\"file1\"; filename=\"$destFilename\"\r\n"
			append outputData "Content-Transfer-Encoding: binary\r\n"
			append outputData "Content-Type: application/octet-stream\r\n"
			append outputData "Content-Range: $numBytes-[expr $numBytes+[string length $data]-1]/$fileLength\r\n"
			append outputData "\r\n$data\r\n--$bound--\r\n"

		    set token [http::geturl $posturl -type "multipart/form-data; boundary=$bound" -query $outputData]
		    set results [parseResponse [http::data $token]]
			
			# puts stdout "Results: $results"
			
			set responseCode [nirvanix::getValue $results ResponseCode]
			if {$responseCode != 0} {
				set errorMsg [nirvanix::getValue $results ErrorMessage]
				puts stdout "ResponseCode: $responseCode"
				puts stdout "Error Message: $errorMsg"
				error $errorMsg
			}
		    http::cleanup $token
			set numBytes [expr $numBytes+[string length $data]]
			puts stdout "Bytes Uploaded: $numBytes"
		}
	    close $fd
	}

	proc nirvanix::downloadFile {path url progressCallback} {
		http::config -useragent "Nirvanix Tcl SDK"
		set bufferSize [expr 1024*1024*4]
		set downloadComplete false
		set offset 0

		set fd [open $path w]
		fconfigure $fd -translation binary

		while {!$downloadComplete} {
			set token [http::geturl $url -blocksize [expr $bufferSize+1] -binary true -headers [list Range "bytes=$offset-[expr $offset+$bufferSize]"]]
			# Handle any xml errors if the result is not 200
			set ncode [http::ncode $token]
			if {($ncode != 200) && ($ncode != 206)} {
				set results [parseResponse [http::data $token] false]
			
				set responseCode [nirvanix::getValue $results ResponseCode]
				if {$responseCode != 0} {
					set httpcode [http::code $token]
					puts stdout "Http Code: $httpcode"
					puts stdout "ResponseCode: $responseCode"
					set errorMsg [nirvanix::getValue $results ErrorMessage]
					puts stdout "Error Message: $errorMsg"
					error $errorMsg
				}

			}
			puts -nonewline $fd [http::data $token]
			upvar #0 $token state
			# incrememnt based on the returned Content-Length
			set offset [expr $offset+$state(totalsize)]
			set max 0
			set contentRange [lindex $state(meta) [expr [lsearch $state(meta) "Content-Range"]+1]]
			set totalFileLength [lindex [split $contentRange "/"] 1]
			puts stdout "Offset: $offset Total: $totalFileLength"
			
			http::cleanup $token
			
			if {$offset >= $totalFileLength} {
				break
			}			
		}
		close $fd
	}
